/*=============================================================================
*
*   Copyright (C) 2020 All rights reserved.
*
*   Filename:		gauss_point.hpp
*
*   Author: Wang Zhecheng - wangzhecheng@yeah.net
*
*   Date: 2020-02-20 01:58
*
*   Last Editors: Wang Zhecheng - wangzhecheng@yeah.net
*
*   Last modified:	2020-08-04 01:23
*
*   Description:
*
=============================================================================*/
#ifndef GAUSSPOINT_H
#define GAUSSPOINT_H

#include "../geometry/shape/point.hpp"
#include "../material/material_interface.hpp"
#include "../util/component.hpp"
#include "../util/record.hpp"
#include <boost/property_tree/ptree.hpp>

namespace geoxfem {
namespace model {

template <typename dimensionType>
struct GaussPoint : traits::point_type<dimensionType>::type {

  using DimensionType = dimensionType;
  using point_type    = typename traits::point_type<DimensionType>::type;
  using Record        = util::iRecord<DimensionType>;
  using Stress        = arithmetic::Stress<DimensionType>;
  using Strain        = arithmetic::Strain<DimensionType>;
  using ptree         = boost::property_tree::ptree;
  using Material      = material::MaterialInterface<DimensionType>;

  static constexpr size_t dimension = traits::dimension<DimensionType>::value;
  using point_type::      operator<;
  using point_type::      operator>;

  /*constructors{{{*/
  GaussPoint(const VectorXR& gcoord, const _real& gweight)
      : point_type{gcoord},
        m_lcoord{},
        m_weight{gweight},
        m_record{},
        m_visibility{} {
    m_record.addField("sigma", Stress{});
    m_record.addField("sigma_tstep", Stress{});
    m_record.addField("epsilon", Strain{});
  }
  GaussPoint()
      : point_type{}, m_lcoord{}, m_weight{0}, m_record{}, m_visibility{} {
    m_record.addField("sigma", Stress{});
    m_record.addField("sigma_tstep", Stress{});
    m_record.addField("epsilon", Strain{});
  }

  GaussPoint(const GaussPoint& rhs) = default;
  GaussPoint(GaussPoint&&)          = default;
  virtual ~GaussPoint()             = default;

  GaussPoint& operator=(const GaussPoint& rhs) {
    m_record = rhs.m_record;
    return *this;
  }
  GaussPoint& operator=(GaussPoint&& rhs) {
    m_record = std::move(rhs.m_record);
    return *this;
  }

  GaussPoint(const point_type& rhs) : point_type{rhs} {}
  GaussPoint(point_type&& rhs) : point_type{std::move(rhs)} {}
  /*}}}*/
  /*tensor related{{{*/

  /** set elemental absolute sigma tensor at previous time step
   *
   * @param value
   */
  void setSigma(const Stress& value) {
    m_record.addField("sigma", value);
  }
  /** self increment elemental absolute sigma tensor at previous time step
   *
   * @param value
   */
  void addSigma(const Stress& value) {
    setSigma(getSigma() + value);
  }
  /** get elemental absolute sigma tensor at previous time step
   *
   * @return
   */
  Stress getSigma() const {
    return m_record.template getValue<Stress>("sigma");
  }

  /** set elemental delta sigma tensor at current time step
   *
   * @param value
   */
  void setSigmaAtTimeStep(const Stress& value) {
    m_record.addField("sigma_tstep", value);
  }
  /** self increment elemental delta sigma tensor at current time step
   *
   * @param value
   */
  void addSigmaAtTimeStep(const Stress& value) {
    setSigmaAtTimeStep(getSigmaAtTimeStep() + value);
  }
  /** get elemental delta sigma tensor at current time step
   *
   * @return
   */
  Stress getSigmaAtTimeStep() const {
    return m_record.template getValue<Stress>("sigma_tstep");
  }

  /** get current elemental absolute sigma
   *
   * @return
   */
  Stress getCurrSigma() const {
    Stress sigma = getSigma() + getSigmaAtTimeStep();
    return sigma;
  }

  /** set elemental epsilon tensor
   *
   * @param value
   */
  void setEpsilon(const Strain& value) {
    m_record.addField("epsilon", value);
  }
  /** add elemental epsilon tensor
   *
   * @param value
   */
  void addEpsilon(const Strain& value) {
    setEpsilon(getEpsilon() + value);
  }
  /** get elemental epsilon tensor
   *
   * @return
   */
  Strain getEpsilon() const {
    return m_record.template getValue<Strain>("epsilon");
  }
  /*}}}*/
  /*member access{{{*/

  /**
   * @brief getLocalCoord
   *
   * @return
   */
  VectorXR getLocalCoord() const {
    return m_lcoord;
  }
  /**
   * @brief setLocalCoord
   *
   * @param lcoord
   */
  void setLocalCoord(const VectorXR& lcoord) {
    m_lcoord = lcoord;
  }
  /**
   * @brief getMaterial
   *
   * @return
   */
  const Material* getMaterial() const {
    return m_material;
  }
  /**
   * @brief setMaterial
   *
   * @param mat
   */
  void setMaterial(const Material* mat) {
    m_material = mat;
  }
  /** get elemental parameter
   *
   * @return
   */
  Record& getParam() {
    return m_record;
  }
  /**
   * @brief getParam
   *
   * @return
   */
  const Record& getParam() const {
    return m_record;
  }
  /** get weight at global coordinates
   *
   * @return
   */
  _real getWeight() const {
    return m_weight;
  }
  /**
   * @brief setWeight
   *
   * @param weight
   */
  void setWeight(const _real& weight) {
    m_weight = weight;
  }
  void addWeight(const _real& weight) {
    m_weight += weight;
  }
  void setInvisible(const size_t& n, const int& value = 1) {
    m_visibility(n) = value;
  }
  void setVisibilityLength(const size_t& n) {
    m_visibility.setZero(n);
  }
  VectorXR getVisibility() const {
    return m_visibility;
  }
  /*}}}*/
  /*arithmetic{{{*/
  GaussPoint& operator+=(const GaussPoint& rhs) {
    m_record += rhs.m_record;
    return *this;
  }
  GaussPoint& operator-=(const GaussPoint& rhs) {
    m_record -= rhs.m_record;
    return *this;
  }
  GaussPoint& operator*=(const _real& rhs) {
    m_record *= rhs;
    return *this;
  }
  GaussPoint& operator/=(const _real& rhs) {
    m_record /= rhs;
    return *this;
  }

  GaussPoint operator+(const GaussPoint& rhs) const {
    GaussPoint gp;
    gp.m_record = m_record + rhs.m_record;
    return gp;
  }
  GaussPoint operator-(const GaussPoint& rhs) const {
    GaussPoint gp;
    gp.m_record = m_record + rhs.m_record;
    return gp;
  }
  GaussPoint operator*(const _real& rhs) const {
    GaussPoint gp;
    gp.m_record = m_record * rhs;
    return gp;
  }
  GaussPoint operator/(const _real& rhs) const {
    GaussPoint gp;
    gp.m_record = m_record / rhs;
    return gp;
  }
  /*}}}*/
  /*misc{{{*/
  void reset() {
    m_record.reset();
  }
  /*}}}*/

private:
  VectorXR        m_lcoord;
  _real           m_weight;
  Record          m_record;
  VectorXR        m_visibility;
  const Material* m_material;
};

template <typename DimensionType>
arithmetic::Stress<DimensionType>
GetCurrSigma(const GaussPoint<DimensionType>& gp) {
  return gp.getCurrSigma();
}
template <typename DimensionType>
arithmetic::Strain<DimensionType>
GetCurrEpsilon(const GaussPoint<DimensionType>& gp) {
  return gp.getEpsilon();
}

}  // namespace model

namespace io {

struct vtu_gauss_coord { /*{{{*/
  template <typename DimensionType>
  std::string operator()(const model::GaussPoint<DimensionType>& gp) const {
    /* Create an output string stream */
    std::ostringstream streamObj;
    /* Add double to stream */
    streamObj << gp.x() << " " << gp.y() << " " << gp.z() << "\n";
    /* Get string from output string stream */
    return streamObj.str();
  }
};                       /*}}}*/
struct vtu_gauss_sigma { /*{{{*/
  template <typename DimensionType>
  std::string operator()(const model::GaussPoint<DimensionType>& gp) const {
    auto stress = model::GetCurrSigma(gp);
    /* Create an output string stream */
    std::ostringstream streamObj;
    /* Add double to stream */
    streamObj << stress(0, 0) << " "   //
              << stress(1, 0) << " "   //
              << stress(2, 0) << "\n"  //
              << stress(0, 1) << " "   //
              << stress(1, 1) << " "   //
              << stress(2, 1) << "\n"  //
              << stress(0, 2) << " "   //
              << stress(1, 2) << " "   //
              << stress(2, 2) << "\n";
    /* Get string from output string stream */
    return streamObj.str();
  }
};                         /*}}}*/
struct vtu_gauss_epsilon { /*{{{*/
  template <typename DimensionType>
  std::string operator()(const model::GaussPoint<DimensionType>& gp) const {
    auto strain = model::GetCurrEpsilon(gp);
    /* Create an output string stream */
    std::ostringstream streamObj;
    /* Add double to stream */
    streamObj << strain(0, 0) << " "   //
              << strain(1, 0) << " "   //
              << strain(2, 0) << "\n"  //
              << strain(0, 1) << " "   //
              << strain(1, 1) << " "   //
              << strain(2, 1) << "\n"  //
              << strain(0, 2) << " "   //
              << strain(1, 2) << " "   //
              << strain(2, 2) << "\n";
    /* Get string from output string stream */
    return streamObj.str();
  }
}; /*}}}*/

}  // namespace io
}  // namespace geoxfem

#endif /* GAUSSPOINT_H */
